Filters
Help users quickly and efficiently narrow down data sets within tables or lists.
#Examples
Filters help users quickly and efficiently narrow down data sets within tables or lists. They provide control and customization, allowing users to focus on the most relevant information.
#Basic usage with single filter
Allows users to refine data based on one specific criterion. Ideal when there's a single primary way users categorize or sort information.
Use Cases:
- Filtering a list of modules (e.g., "DCI”, “Accessibility“, “Quality Assurance“).
- Filtering search results by content (e.g., "Title", "URL").
type Country = { id: string; name: string };
const countries: Country[] = [
{ id: "DK", name: "Denmark" },
{ id: "SE", name: "Sweden" },
{ id: "NO", name: "Norway" },
{ id: "FI", name: "Finland" },
];
const [country, setCountry] = useState<Country | undefined>(countries[0]);
const onChange = (newValue: Country | undefined) => {
console.log("Filter changed, calling API with new country", newValue);
setCountry(newValue);
};
const [filterButton, activeFilters] = useSingleFilter(country, onChange, {
label: "Country",
stringify: (country) => country?.name,
items: countries.map((country) => ({ title: country.name, value: country })),
compareFn: (a, b) => a.id === b.id,
});
return <TableToolbar filter={filterButton} activeFilters={activeFilters} />;
#Basic usage with filter group
Combines multiple filters for applying several criteria simultaneously. Great for complex data sets with varying user needs. The criteria for filtering should be well-defined and relatively stable.
Use Cases:
- Filtering an issue list by decisions, conformance, and difficulty
- Filtering active policies by priority, category, module and access.
// these types look ambiguous, but they are just examples
// of how to use filters with objects, but you can use any
// type you want, including primitives
type BaseFilter = { id: string; name: string };
type Country = BaseFilter;
type Device = BaseFilter;
type Browser = BaseFilter;
type Filters = {
country: Country;
device?: Device;
browsers?: Browser[];
};
const countries: Country[] = [
{ id: "DK", name: "Denmark" },
{ id: "SE", name: "Sweden" },
{ id: "NO", name: "Norway" },
{ id: "FI", name: "Finland" },
];
const devices: Device[] = [
{ id: "desktop", name: "Desktop" },
{ id: "mobile", name: "Mobile" },
{ id: "tablet", name: "Tablet" },
{ id: "laptop", name: "Laptop" },
];
const browsers: Browser[] = [
{ id: "chrome", name: "Chrome" },
{ id: "edge", name: "Edge" },
{ id: "firefox", name: "Firefox" },
{ id: "safari", name: "Safari" },
];
const [filters, setFilters] = useState<Filters>({
country: countries[0],
device: devices[1],
browsers: [browsers[0], browsers[1]],
});
const onChange = (newFilters: Filters) => {
console.log("Filters changed, calling API with new filters", newFilters);
setFilters(newFilters);
};
const [filterGroup, activeFilters] = useFilterGroup<Filters>(filters, onChange, [
{
label: "Country",
property: "country",
items: countries.map((country) => ({ title: country.name, value: country })),
compareFn: (a, b) => a?.id === b?.id,
stringify: ({ country }) => country?.name,
},
{
label: "Device",
property: "device",
items: devices.map((device) => ({ title: device.name, value: device })),
compareFn: (a, b) => a?.id === b?.id,
stringify: ({ device }) => device?.name,
},
{
label: "Browsers",
property: "browsers",
items: browsers.map((browser) => ({ title: browser.name, value: browser })),
compareFn: (a, b) => a?.id === b?.id,
searchable: "always",
stringify: ({ browsers }) =>
(browsers || []).length > 0 ? browsers?.map((b) => b.name).join(", ") : undefined,
},
]);
return <TableToolbar filter={filterGroup} activeFilters={activeFilters} />;
#Usage with default filter value
Setting default filter values can streamline initial interactions and guide users toward common starting points.
Use Cases:
- Setting the default data filter to "All categories" in Core Wins
- Setting the default sort order to "Impact: High to low" in SEO > Insight .
When using useFilterGroup
or useSingleFilter
, you can pass a default value to the filter by using the defaultOption
property in the filter's definition. This is useful when you want to set a default value for a filter, for example when the user has not interacted with the filter yet. Also, whenever the user clears the filter, the default value will be set again.
Best Practices:
- Choose default values that are relevant and helpful to the majority of users.
- Clearly indicate that a filter has a default value (e.g., using static Pill).
- Allow users to easily change the default value.
#Single filter
type Country = { id: string; name: string };
const countries: Country[] = [
{ id: "DK", name: "Denmark" },
{ id: "SE", name: "Sweden" },
{ id: "NO", name: "Norway" },
{ id: "FI", name: "Finland" },
];
const [country, setCountry] = useState<Country | undefined>(countries[0]);
const onChange = (newValue: Country | undefined) => {
console.log("Filter changed, calling API with new country", newValue);
setCountry(newValue);
};
const [filterButton, activeFilters] = useSingleFilter(country, onChange, {
label: "Country",
name: "country",
defaultOption: countries[0],
stringify: (country) => country?.name,
items: countries.map((country) => ({ title: country.name, value: country })),
compareFn: (a, b) => a.id === b.id,
});
return <TableToolbar filter={filterButton} activeFilters={activeFilters} />;
#Filter group
const countries = [
{ id: "DK", name: "Denmark" },
{ id: "SE", name: "Sweden" },
{ id: "NO", name: "Norway" },
{ id: "FI", name: "Finland" },
];
const devices = [
{ id: "desktop", name: "Desktop" },
{ id: "mobile", name: "Mobile" },
{ id: "tablet", name: "Tablet" },
{ id: "laptop", name: "Laptop" },
];
const [filters, setFilters] = useState({
country: countries[0],
devices: [devices[0], devices[1]],
});
const [filterGroup, activeFilters] = useFilterGroup(filters, setFilters, [
{
label: "Country",
name: "country",
property: "country",
defaultOption: countries[0],
stringify: ({ country }) => country?.name,
items: countries.map((country) => ({ title: country.name, value: country })),
compareFn: (a, b) => a.id === b.id,
},
{
label: "Devices",
name: "devices",
property: "devices",
stringify: ({ devices }) =>
(devices || []).length > 0 ? devices?.map((b) => b.name).join(", ") : undefined,
items: devices.map((device) => ({ title: device.name, value: device })),
compareFn: (a, b) => a.id === b.id,
},
]);
return <TableToolbar filter={filterGroup} activeFilters={activeFilters} />;
#Usage with data-observe-keys
The data-observe-key
attribute allows you to track user interactions with the filters. This can be valuable for analytics and understanding user behavior.
Use Cases:
- Tracking which filters are most frequently used.
- Analyzing how users combine different filters.
- Identifying potential usability issues with the filtering process.
Use data-observe-key
on the main component and/or individual filters to create identifiers, which are useful for tracking user interactivity, for example. Inner buttons and components, such as confirm, cancel, clear, pills, will be assigned with the same data-observe-key
plus a discriminator. For instance, when using useFilterGroup(..., ..., ..., { "data-observe-key": "foo" })
, the clear all button will be assigned with foo-ClearAll
. See below a complete example and note this pattern in action.
Best Practices:
- Assign unique
data-observe-key
values to each filter and its sub-components. - Use a consistent naming convention for
data-observe-key
values. Read more in Process for Tagging Elements (Data-observe-keys) - Ensure that the
data-observe-key
values are meaningful and easy to interpret.
const countries = [
{ id: "DK", name: "Denmark", "data-observe-key": "CountryFilter-DK" },
{ id: "SE", name: "Sweden", "data-observe-key": "CountryFilter-SE" },
{ id: "NO", name: "Norway", "data-observe-key": "CountryFilter-NO" },
{ id: "FI", name: "Finland", "data-observe-key": "CountryFilter-FI" },
];
const devices = [
{ id: "desktop", name: "Desktop", "data-observe-key": "DeviceFilter-desktop" },
{ id: "mobile", name: "Mobile", "data-observe-key": "DeviceFilter-mobile" },
{ id: "tablet", name: "Tablet", "data-observe-key": "DeviceFilter-tablet" },
{ id: "laptop", name: "Laptop", "data-observe-key": "DeviceFilter-laptop" },
];
const [filters, setFilters] = useState({
country: countries[0],
devices: [devices[0], devices[1]],
});
const [filterGroup, activeFilters] = useFilterGroup(
filters,
setFilters,
[
{
label: "Country",
name: "country",
property: "country",
stringify: ({ country }) => country?.name,
items: countries.map((country) => ({
title: country.name,
value: country,
"data-observe-key": country["data-observe-key"],
})),
compareFn: (a, b) => a.id === b.id,
"data-observe-key": "CountryFilter",
},
{
label: "Devices",
name: "devices",
property: "devices",
stringify: ({ devices }) =>
(devices || []).length > 0 ? devices?.map((b) => b.name).join(", ") : undefined,
items: devices.map((device) => ({
title: device.name,
value: device,
"data-observe-key": device["data-observe-key"],
})),
compareFn: (a, b) => a.id === b.id,
"data-observe-key": "DeviceFilter",
},
],
{
"data-observe-key": "FilterGroup-Example",
}
);
return <TableToolbar filter={filterGroup} activeFilters={activeFilters} />;
#Guidelines
#Best practices
#Do not use when
#Accessibility
Explore detailed guidelines for this component: Accessibility Specifications